libobs_simple\sources\windows\sources/
monitor_capture.rs

1//! Monitor capture source for Windows using libobs-rs
2//! This source captures the entire monitor and is used for screen recording.
3
4use super::ObsDisplayCaptureMethod;
5use crate::define_object_manager;
6/// Note: This does not update the capture method directly, instead the capture method gets
7/// stored in the struct. The capture method is being set to WGC at first, then the source is created and then the capture method is updated to the desired method.
8use display_info::DisplayInfo;
9use libobs_simple_macro::obs_object_impl;
10use libobs_wrapper::{
11    data::{ObsObjectBuilder, ObsObjectUpdater},
12    scenes::ObsSceneRef,
13    sources::{ObsSourceBuilder, ObsSourceRef},
14    unsafe_send::Sendable,
15    utils::ObsError,
16};
17use num_traits::ToPrimitive;
18use std::ffi::CStr;
19
20// Usage example
21define_object_manager!(
22    /// Provides an easy-to-use builder for the monitor capture source.
23    #[derive(Debug)]
24    struct MonitorCaptureSource("monitor_capture") for ObsSourceRef {
25        #[obs_property(type_t = "string", settings_key = "monitor_id")]
26        monitor_id_raw: String,
27
28        #[obs_property(type_t = "bool")]
29        /// Sets whether the cursor should be captured.
30        capture_cursor: bool,
31
32        #[obs_property(type_t = "bool")]
33        /// Compatibility mode for the monitor capture source.
34        compatibility: bool,
35
36        #[obs_property(type_t = "bool")]
37        /// If the capture should force SDR
38        force_sdr: bool,
39
40        capture_method: Option<ObsDisplayCaptureMethod>,
41    }
42);
43
44const AUDIO_SOURCE_TYPE: &CStr = c"wasapi_process_output_capture";
45fn audio_capture_available() -> bool {
46    unsafe { !libobs::obs_get_latest_input_type_id(AUDIO_SOURCE_TYPE.as_ptr()).is_null() }
47}
48
49#[obs_object_impl]
50impl MonitorCaptureSource {
51    /// Gets all available monitors
52    pub fn get_monitors() -> anyhow::Result<Vec<Sendable<DisplayInfo>>> {
53        Ok(DisplayInfo::all()?.into_iter().map(Sendable).collect())
54    }
55
56    pub fn set_monitor(self, monitor: &Sendable<DisplayInfo>) -> Self {
57        self.set_monitor_id_raw(monitor.0.name.as_str())
58    }
59
60    pub fn set_capture_audio(mut self, capture_audio: bool) -> anyhow::Result<Self, String> {
61        if capture_audio && !audio_capture_available() {
62            return Err("Game Audio Capture is not available on this system".to_string());
63        }
64
65        self.get_settings_updater()
66            .set_bool_ref("capture_audio", capture_audio);
67
68        Ok(self)
69    }
70}
71
72impl<'a> MonitorCaptureSourceUpdater<'a> {
73    pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Self {
74        self.get_settings_updater()
75            .set_int_ref("method", method.to_i32().unwrap() as i64);
76
77        self
78    }
79}
80
81impl MonitorCaptureSourceBuilder {
82    /// Sets the capture method for the monitor capture source.
83    /// Only MethodWgc works for now as the other DXGI method does not work and only records a black screen (Failed to DuplicateOutput1)
84    /// Workaround for black screen bug: [issue](https://github.com/libobs-rs/libobs-rs/issues/5)
85    pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Self {
86        self.capture_method = Some(method);
87        self
88    }
89}
90
91impl ObsSourceBuilder for MonitorCaptureSourceBuilder {
92    fn add_to_scene(mut self, scene: &mut ObsSceneRef) -> Result<ObsSourceRef, ObsError>
93    where
94        Self: Sized,
95    {
96        // Because of a black screen bug, we need to set the method to WGC first and then update
97        self.get_settings_updater().set_int_ref(
98            "method",
99            ObsDisplayCaptureMethod::MethodWgc.to_i32().unwrap() as i64,
100        );
101
102        let method_to_set = self.capture_method;
103        let runtime = self.runtime.clone();
104
105        let b = self.build()?;
106        let mut res = scene.add_source(b)?;
107
108        if let Some(method) = method_to_set {
109            MonitorCaptureSourceUpdater::create_update(runtime, &mut res)?
110                .set_capture_method(method)
111                .update()?;
112        }
113
114        Ok(res)
115    }
116}